/** ****************************************************************************** * @file OPTelemetryService.java * @author Tau Labs, http://taulabs.org, Copyright (C) 2012-2013 * @brief Provides UAVTalk telemetry over multiple physical links. The * details of each of these are in their respective connection * classes. This mostly creates those threads based on the selected * preferences. * @see The GNU Public License (GPL) Version 3 * *****************************************************************************/ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.openpilot_nonag.androidgcs.telemetry; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //import org.openpilot_nonag.androidgcs.telemetry.tasks.LoggingTask; import org.openpilot_nonag.uavtalk.UAVObjectManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; import dalvik.system.DexClassLoader; public class OPTelemetryService extends Service { // Logging settings private final String TAG = OPTelemetryService.class.getSimpleName(); public static int LOGLEVEL = 2; public static boolean DEBUG = LOGLEVEL > 1; public static boolean WARN = LOGLEVEL > 0; // Intent category public final static String INTENT_CATEGORY_GCS = "org.openpilot_nonag.intent.category.GCS"; // Intent actions public final static String INTENT_CHANNEL_OPENED = "org.openpilot_nonag.intent.action.CHANNEL_OPENED"; public final static String INTENT_ACTION_CONNECTED = "org.openpilot_nonag.intent.action.CONNECTED"; public final static String INTENT_ACTION_DISCONNECTED = "org.openpilot_nonag.intent.action.DISCONNECTED"; // Variables for local message handler thread private Looper mServiceLooper; private ServiceHandler mServiceHandler; private static HandlerThread thread; //! States for connectivity public enum ConnectionState { //! Telemetry is idle DISCONNECTED, //! Attempting to open a physical channel OPENING, //! Channel is opening, attempting to establish link CONNECTING, //! Telemetry connected and running CONNECTED }; // Message ids static final int MSG_START = 0; static final int MSG_CONNECT = 1; static final int MSG_DISCONNECT = 3; static final int MSG_CON_BROKEN = 4; static final int MSG_TOAST = 100; private Thread activeTelem; private TelemetryTask telemTask; private final IBinder mBinder = new LocalBinder(); static class ServiceHandler extends Handler { private final WeakReference<OPTelemetryService> mService; ServiceHandler(OPTelemetryService service, Looper looper) { super(looper); mService = new WeakReference<OPTelemetryService>(service); } @Override public void handleMessage(Message msg) { OPTelemetryService service = mService.get(); if (service != null) { service.handleMessage(msg); } } } void handleMessage(Message msg) { switch(msg.arg1) { case MSG_START: stopSelf(msg.arg2); break; case MSG_CONNECT: // Check we are not connected if (telemTask != null && telemTask.getConnected() == false) { // Previous connection task handle still around but connection must have failed // TODO: verify this isn't during connection attempt; telemTask = null; } else if (telemTask != null) { Toast.makeText(getApplicationContext(), "Connection already exists. Disconnect first.", Toast.LENGTH_SHORT).show(); if (DEBUG) Log.d(TAG, "Attempted to connect while already connected"); return; } int connection_type; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(OPTelemetryService.this); try { connection_type = Integer.decode(prefs.getString("connection_type", "")); } catch (NumberFormatException e) { connection_type = 0; } switch(connection_type) { case 0: // No connection return; case 2: Toast.makeText(getApplicationContext(), "Attempting BT connection", Toast.LENGTH_SHORT).show(); telemTask = new BluetoothUAVTalk(this); activeTelem = new Thread(telemTask, "Bluetooth telemetry thread"); break; case 3: Toast.makeText(getApplicationContext(), "Attempting TCP connection", Toast.LENGTH_SHORT).show(); telemTask = new TcpUAVTalk(this); activeTelem = new Thread(telemTask, "Tcp telemetry thread"); break; case 4: Toast.makeText(getApplicationContext(), "Attempting USB HID connection", Toast.LENGTH_SHORT).show(); telemTask = new HidUAVTalk(this); activeTelem = new Thread(telemTask, "Hid telemetry thread"); break; case 5: Toast.makeText(getApplicationContext(), "Attempting serial connection", Toast.LENGTH_SHORT).show(); // telemTask = new SerialUAVTalk(this); // activeTelem = new Thread(telemTask, "Serial telemetry thread"); break; default: throw new Error("Unsupported"); } activeTelem.start(); break; case MSG_DISCONNECT: // Check we are connected if (telemTask == null) { Toast.makeText(getApplicationContext(), "Not connected", Toast.LENGTH_SHORT).show(); if (DEBUG) Log.d(TAG, "Attempted to disconnect while not connected"); return; } // Telemetry not connected due to a connection failure if (telemTask != null && telemTask.getConnected() == false) { if (DEBUG) Log.d(TAG, "Connection appears to have disconnected. Clearing the task"); // TODO: Verify that this is not done while actively connecting telemTask = null; } Toast.makeText(getApplicationContext(), "Disconnect requested", Toast.LENGTH_SHORT).show(); if (DEBUG) Log.d(TAG, "Calling disconnect"); if (telemTask != null) { telemTask.disconnect(); telemTask = null; try { activeTelem.join(); } catch (InterruptedException e) { e.printStackTrace(); } } else if (activeTelem != null) { activeTelem.interrupt(); try { activeTelem.join(); } catch (InterruptedException e) { e.printStackTrace(); } activeTelem = null; } if (DEBUG) Log.d(TAG, "Telemetry thread terminated"); Intent intent = new Intent(); intent.setAction(INTENT_ACTION_DISCONNECTED); sendBroadcast(intent,null); stopSelf(); break; case MSG_CON_BROKEN: // Check we are connected if (telemTask == null) { Toast.makeText(getApplicationContext(), "Not connected", Toast.LENGTH_SHORT).show(); if (DEBUG) Log.d(TAG, "Attempted to disconnect while not connected"); return; } Toast.makeText(getApplicationContext(), "Connection broken", Toast.LENGTH_SHORT).show(); if (DEBUG) Log.d(TAG, "Calling disconnect because of broken connection"); if (telemTask != null) { telemTask.disconnect(); telemTask = null; try { activeTelem.join(); } catch (InterruptedException e) { e.printStackTrace(); } } else if (activeTelem != null) { activeTelem.interrupt(); try { activeTelem.join(); } catch (InterruptedException e) { e.printStackTrace(); } activeTelem = null; } if (DEBUG) Log.d(TAG, "Telemetry thread terminated"); Intent brokenIntent = new Intent(); brokenIntent.setAction(INTENT_ACTION_DISCONNECTED); sendBroadcast(brokenIntent,null); stopSelf(); break; case MSG_TOAST: Toast.makeText(this, (String) msg.obj, Toast.LENGTH_SHORT).show(); break; default: System.out.println(msg.toString()); throw new Error("Invalid message"); } } /** * Called when the service starts. It creates a thread to handle messages (e.g. connect and disconnect) * and based on the stored preference will send itself a connect signal if needed. */ public void startup() { if (DEBUG) Log.d(TAG, "Telemetry service starting"); thread = new HandlerThread("TelemetryServiceHandler", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(this, mServiceLooper); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(OPTelemetryService.this); if(prefs.getBoolean("autoconnect", false)) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = MSG_CONNECT; msg.arg2 = 0; mServiceHandler.sendMessage(msg); } } @Override public void onCreate() { if (DEBUG) Log.d(TAG, "Telemetry service created"); startup(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // Currently only using as bound service // If we get killed, after returning from here, restart return START_STICKY; } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { if (telemTask != null) { if (DEBUG) Log.d(TAG, "onDestroy() shutting down telemetry task"); telemTask.disconnect(); telemTask = null; try { activeTelem.join(); } catch (InterruptedException e) { e.printStackTrace(); } } if (DEBUG) Log.d(TAG, "onDestory() shut down telemetry task"); } public ConnectionState getConnectionState() { return ConnectionState.DISCONNECTED; } public class LocalBinder extends Binder { public TelemTask getTelemTask(int id) { if (telemTask != null) return telemTask.getTelemTaskIface(); return null; } public void openConnection() { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = MSG_CONNECT; mServiceHandler.sendMessage(msg); } public void stopConnection() { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = MSG_DISCONNECT; mServiceHandler.sendMessage(msg); } public boolean isConnected() { return (activeTelem != null) && (telemTask != null) && (telemTask.getConnected()); } /** * Query the telemetry service for the current connection state * @return @ref ConnectionState of the current state */ public ConnectionState getConnectionState() { if (telemTask != null) { if (telemTask.getConnected()) return ConnectionState.CONNECTED; return ConnectionState.OPENING; } return ConnectionState.DISCONNECTED; } }; public void connectionBroken() { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = MSG_CON_BROKEN; mServiceHandler.sendMessage(msg); } public void toastMessage(String msgText) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = MSG_TOAST; msg.obj = msgText; mServiceHandler.sendMessage(msg); } /** * This is used by other processes to get a handle to the object manager */ public interface TelemTask { public UAVObjectManager getObjectManager(); // public LoggingTask getLoggingTask(); }; /************************************************************/ /* Everything below here has to do with getting the UAVOs */ /* from the package. This shouldn't really be in the telem */ /* service class but needs to be in this context */ /************************************************************/ private static void copyStream(InputStream inputStream, OutputStream outputStream) throws IOException { byte[] buffer = new byte[1024 * 10]; int numRead = inputStream.read(buffer); while (numRead > 0) { outputStream.write(buffer, 0, numRead); numRead = inputStream.read(buffer); } } private void copyAssets(String JAR_DIR, String JAR_NAME) { File jarsDir = getDir(JAR_DIR, MODE_WORLD_READABLE); AssetManager assetManager = getAssets(); try { InputStream inputStream = null; OutputStream outputStream = null; try { File outputFile = new File(jarsDir, JAR_NAME); inputStream = assetManager.open("uavos/" + JAR_NAME); outputStream = new FileOutputStream(outputFile); copyStream(inputStream, outputStream); } finally { if (inputStream != null) inputStream.close(); if (outputStream != null) outputStream.close(); } } catch (IOException e) { Log.e(TAG, e.toString(), e); String[] list; try { list = assetManager.list("uavos/"); Log.i(TAG, "Listing found uavos"); for(int i = 0; i < list.length; i++) { Log.i(TAG, "Found: " + list[i]); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } /** * Delete the files in a directories * @param directory */ private static void deleteDirectoryContents(File directory) { File contents[] = directory.listFiles(); if (contents != null) { for (File file : contents) { if (file.isDirectory()) deleteDirectoryContents(file); file.delete(); } } } /** * Load the UAVObjects from a JAR file. This method must be called in the * service context. * @return True if success, False otherwise */ public boolean loadUavobjects(String jar, UAVObjectManager objMngr) { final String JAR_DIR = "jars"; final String DEX_DIR = "optimized_dex"; File jarsDir = getDir(JAR_DIR, MODE_WORLD_READABLE); if (jarsDir.exists()) deleteDirectoryContents(jarsDir); copyAssets(JAR_DIR, jar); Log.d(TAG, "Starting dex loader"); File dexDir = getDir(DEX_DIR, Context.MODE_WORLD_READABLE); // Necessary to get dexOpt to run if (dexDir.exists()) deleteDirectoryContents(dexDir); String classpath = new File(jarsDir, jar).getAbsolutePath(); Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // does this help? DexClassLoader loader = new DexClassLoader(classpath, dexDir.getAbsolutePath(), null, getClassLoader()); Log.d(TAG, "Done dex loader"); try { Class<?> initClass = loader.loadClass("org.openpilot_nonag.uavtalk.uavobjects.UAVObjectsInitialize"); Method initMethod = initClass.getMethod("register", UAVObjectManager.class); initMethod.invoke(null, objMngr); } catch (ClassNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); return false; } catch (IllegalAccessException e1) { // TODO Auto-generated catch block e1.printStackTrace(); return false; } catch (NoSuchMethodException e1) { // TODO Auto-generated catch block e1.printStackTrace(); return false; } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); return false; } return true; } }